use std::ffi::{OsStr, OsString};

use crate::{runtime::FuncBinds, Env, Value};

use super::Builtin;

pub fn bind(env: &mut FuncBinds) {
    env.bind_builtin("sconcat", StringConcat);
}

enum StringMaker {
    String(String),
    OsString(OsString),
}

impl StringMaker {
    fn push_str(&mut self, s: &str) {
        match self {
            StringMaker::String(string) => {
                string.push_str(s);
            }
            StringMaker::OsString(os_string) => {
                os_string.push(s);
            }
        }
    }

    fn push_os_str(&mut self, s: &OsStr) {
        match self {
            StringMaker::String(string) => {
                let mut string: OsString = std::mem::take(string).into();
                string.push(s);
                *self = StringMaker::OsString(string)
            }
            StringMaker::OsString(os_string) => {
                os_string.push(s);
            }
        }
    }

    fn push_value(&mut self, each: &Value) {
        match each {
            Value::Bool(b) => {
                self.push_str(&b.to_string());
            }
            Value::Symbol(symbol) => {
                self.push_str(symbol);
            }
            Value::String(string) => {
                self.push_str(string);
            }
            Value::Integer(n) => {
                self.push_str(&n.to_string());
            }
            Value::Path(path_buf) => {
                self.push_os_str(path_buf.as_os_str());
            }
            Value::OsString(path_buf) => {
                self.push_os_str(path_buf);
            }
            Value::Array(vec) => {
                for each in vec {
                    self.push_value(each);
                }
            }
            Value::Hash(_) => todo!(),
        }
    }
}

struct StringConcat;

impl Builtin for StringConcat {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let mut s = StringMaker::String(String::new());

        for each in argv {
            s.push_value(each);
        }

        Ok(match s {
            StringMaker::String(string) => Value::String(string),
            StringMaker::OsString(os_string) => Value::OsString(os_string),
        })
    }
}
